/**
 * 表达式计算器
 * 本工具可用于禁用eval()与new Function()的微信小程序中；
 * 使用方法: new Calculator().calc('sin(10) * cos(10) * pow(2, 3)');
 * 数学函数支持JavaScript中所有的同名数学方法 Math.*()，但调用时不包含"Math.";
 */
export default class Calculator {
    constructor() { }

    calc(expression) {
        if (!this.checkExpression(expression)) 
            return '表达式错误';
        return this.calcExpression(this.splitExpression(expression));
    }

    checkExpression(expression) {
        let st = new Stack();
        let ans = 0;
        let symbole_ans = 0;
        for (let i=0;i<expression.length;i++) {
            let c = expression[i];
            // 检查括号
            if (c === '(') {
                st.push(c);
                ans ++;
            } else if (c === ')') {
                if (st.getTop() === '(')
                    st.pop();
                ans --;
            }
            // 检查符号
            if ('+-*/'.indexOf(c) >= 0) {
                if (++ symbole_ans >= 3)
                    return false;
            } else if (c !== ' ') {
                symbole_ans = 0;
            }
        }
        return ans === 0;
    }

    splitExpression(express) {
        // 清除空格
        let tmp_express = [];
        for (let i=0;i<express.length;i++) {
            if (express[i] !== ' ') {
                tmp_express.push(express[i]);
            }
        }
        express = tmp_express.join('');
        
        if (express === '') return [];
        let a = [];
        // 分割表达式
        for (let i=0;i<express.length;i++) {
            if ((express[i] >= '0' && express[i] <= '9') || express[i] === '.') {
                // 分割数字
                let startPos = i;
                while ((express[i] >= '0' && express[i] <= '9') || express[i] === '.') i++;
                let val = express.substring(startPos, i --);
                a.push(Number(val));
            } else if ((express[i] >= 'a' && express[i] <= 'z') || (express[i] >= 'A' && express[i] <= 'Z')) {
                // 分割函数
                let startPos = i;
                while (i < express.length && '(+-*/)'.indexOf(express[i]) < 0) i++;
                let val = express.substring(startPos, i --);
                a.push(val);
            } else if ('+-*/()!%'.indexOf(express[i]) >= 0) {
                // 分割运算符
                a.push(express[i]);
            }
        }
        // 给负数添加括号
        let st_tmp = new Stack();
        let st = new Stack();
        for (let i=0;i<a.length;i++) {
            if (a[i] === '-' && '+-*/'.indexOf(st_tmp.getTop()) >= 0) {
                st_tmp.push('('); st_tmp.push(0); st_tmp.push(a[i]); st_tmp.push(a[++i]); st_tmp.push(')');
            } else {
                st_tmp.push(a[i]);
            }
        }
        while (!st_tmp.isEmpty()) st.push(st_tmp.pop());
        for (a=[]; !st.isEmpty(); a.push(st.pop()));
        return a;
    }

    calcExpression(a = []) {
        if (a.length === 0) return 0;
        // this.PolishValue = [];

        const priority = { '*':3, '/':3, '+':2, '-':2, '(':1, ')':0 };

        let st1 = new Stack(); // 符号栈
        let st2 = new Stack(); // 数字栈

        for (let i=0;i<a.length;i++) {
            let c = a[i];
            if ('+-*/()!%'.indexOf(c) >= 0 || this.isFunction(c)) {
                if ('!%'.indexOf(c) >= 0) {
                    // 单目运算符
                    st2.push(this.calcSymbol(c, st2.pop()));
                    continue;
                }
                if (c === '(' || this.isFunction(c)) {
                    st1.push(c);
                } else if (c === ')') {
                    // 符号一直退栈计算直到左括号为止
                    while (!st1.isEmpty() && st1.getTop() !== '(') 
                        this.popAndCalc(st1, st2);
                    st1.pop();
                    // 计算函数
                    if (!st1.isEmpty() && this.isFunction(st1.getTop()))
                        st2.push(this.calcFunction(st1.pop(), st2));
                } else {
                    while (priority[st1.getTop()] >= priority[c])
                        this.popAndCalc(st1, st2);
                    st1.push(c);
                }
            } else {
                st2.push(c);
                // this.PolishValue.push(c);
            }
        }
        while (!st1.isEmpty()) {
            this.popAndCalc(st1, st2);
        }
        return st2.pop();
    }

    popAndCalc(symbolStack, numStack) {
        let symbol = symbolStack.pop();
        let num2 = numStack.pop();
        let num1 = numStack.pop();
        let result = this.calcSymbol(symbol, Number(num1), Number(num2));
        numStack.push(result);
    }

    isFunction(c) {
        return ((c[0]>='a'&&c[0]<='z') || (c[0]>='A'&&c[0]<='Z'));
    }

    calcSymbol(symbol, ...nums) {
        // this.PolishValue.push(symbol);
        switch (symbol) {
            case '+' : return nums[0] + nums[1];
            case '-' : return nums[0] - nums[1];
            case '*' : return nums[0] * nums[1];
            case '/' : return 1.0 * nums[0] / nums[1];
            case '%' : return 0.01 * nums[0];
            case '!' : // 计算阶乘
                let multi = 1;
                for (let i=1; i<=nums[0] && multi!==Infinity; multi *= i++);
                return multi;
        }
    }

    calcFunction(name, numStack) {
        let arg_num = Math[name].length; 
        let st = new Stack();
        for (let i=0; i<arg_num; i++, st.push(numStack.pop()));
        switch (arg_num) {
            case 0 : return Math[name]();
            case 1 : return Math[name](st.pop());
            case 2 : return Math[name](st.pop(), st.pop());
            case 3 : return Math[name](st.pop(), st.pop(), st.pop());
        }
    }

}
/**
 * 链式节点
 */
class LinkNode {
    data = null;
    next = null;
}
/**
 * 栈结构
 */
class Stack {
    constructor () {
        this.init();
    }
    init() {
        this.head = new LinkNode();
        this.top = this.head;
        this.length = 0;
    }
    isEmpty() {
        return this.top.next === null;
    }
    push(data) {
        let node = new LinkNode();
        node.data = data;
        node.next = this.top;
        this.top = node;
        this.length ++;
    }
    pop() {
        if (this.isEmpty()) return null;
        let node = this.top;
        this.top = this.top.next;
        node.next = null;
        this.length --;
        return node.data;
    }
    getTop() {
        return this.top.data;
    }
    clear() {
        while (!this.isEmpty()) this.pop();
    }
}